#include "lua.h"
#include "lauxlib.h"
#include "lualib.h"
#include "cm_fs.h"
#include <stdlib.h>
#include "lua_scheduler.h"

// Lua: status, err = sys.dofile(filepath)
static int l_sys_dofile(lua_State *L) {
    const char *filepath = luaL_checkstring(L, 1);

    int32_t size = cm_fs_filesize(filepath);
    if (size < 0) {
        lua_pushboolean(L, 0);
        lua_pushfstring(L, "cannot get size of file %s", filepath);
        return 2;
    }

    if (size == 0) { // Empty file is a valid script
        luaL_loadstring(L, "");
        lua_pcall(L, 0, 0, 0);
        lua_pushboolean(L, 1);
        return 1;
    }

    int32_t fd = cm_fs_open(filepath, CM_FS_RB);
    if (fd < 0) {
        lua_pushboolean(L, 0);
        lua_pushfstring(L, "cannot open file %s", filepath);
        return 2;
    }

    char *buffer = (char *)malloc(size);
    if (!buffer) {
        cm_fs_close(fd);
        lua_pushboolean(L, 0);
        lua_pushstring(L, "not enough memory to read file");
        return 2;
    }

    int32_t bytes_read = cm_fs_read(fd, buffer, size);
    cm_fs_close(fd);

    if (bytes_read != size) {
        free(buffer);
        lua_pushboolean(L, 0);
        lua_pushstring(L, "failed to read entire file");
        return 2;
    }

    if (luaL_loadbuffer(L, buffer, bytes_read, filepath) != LUA_OK) {
        free(buffer);
        // Error message is already on the stack
        lua_pushboolean(L, 0);
        lua_insert(L, -2);
        return 2;
    }

    free(buffer);

    if (lua_pcall(L, 0, LUA_MULTRET, 0) != LUA_OK) {
        // Error message is already on the stack
        lua_pushboolean(L, 0);
        lua_insert(L, -2);
        return 2;
    }

    lua_pushboolean(L, 1);
    lua_insert(L, 1);
    return lua_gettop(L);
}

// Lua: status, err = sys.loadstring(code_string)
static int l_sys_loadstring(lua_State *L) {
    size_t len;
    const char *code = luaL_checklstring(L, 1, &len);

    if (luaL_loadstring(L, code) != LUA_OK) {
        // Error message is already on the stack
        lua_pushboolean(L, 0);
        lua_insert(L, -2);
        return 2;
    }

    if (lua_pcall(L, 0, LUA_MULTRET, 0) != LUA_OK) {
        // Error message is already on the stack
        lua_pushboolean(L, 0);
        lua_insert(L, -2);
        return 2;
    }

    lua_pushboolean(L, 1);
    lua_insert(L, 1);
    return lua_gettop(L);
}

// Lua: sys.task(function, ...args)
static int l_sys_task(lua_State *L) {
    return lua_scheduler_new_task(L);
}

static const luaL_Reg sys_lib[] = {
    {"dofile", l_sys_dofile},
    {"loadstring", l_sys_loadstring},
    {"task", l_sys_task},
    {NULL, NULL}
};

int luaopen_sys(lua_State *L) {
    luaL_newlib(L, sys_lib);
    return 1;
}
